package logic

import (
	"cloud-entropy-storage-system/common/pb/pbfiles"
	"cloud-entropy-storage-system/common/pkg/format"
	"cloud-entropy-storage-system/common/pkg/hostinfo"
	"cloud-entropy-storage-system/common/pkg/pathdir"
	"cloud-entropy-storage-system/common/pkg/snow"
	"context"
	"errors"
	"fmt"
	"github.com/alexeyco/simpletable"
	"github.com/asdine/storm/v3"
	"go.uber.org/zap"
	"io/ioutil"
	"log"
	"os"

	"strconv"
	"strings"
	"sync"
	"time"

	"google.golang.org/grpc"
)

type Manager struct {
	HttpHost        string
	DataDir         string
	DataCenter      string
	Machine         string
	DiskFree        uint64
	DiskUsed        uint64
	DiskSize        uint64
	MaxDiskUsed     uint64
	HeartWait       int
	HeartTimeout    int
	Writeable       bool
	ServerOffline   bool
	VolumeNum       uint32
	MaxVolSize      uint64
	CanCreateVolume bool
	Volumes         map[uint64]*Volume
	Conn            *grpc.ClientConn
	lock            sync.Mutex
	db              *storm.DB
	log             *zap.Logger
	*pbfiles.UnimplementedUploadRpcServer
	*pbfiles.UnimplementedVolumeFileServer
}

type NewVolumeReq struct {
	HttpHost     string
	DataCenter   string
	StoreName    string
	Dir          string
	HeartWait    int
	HeartTimeout int
	MaxVolSize   uint64
	MaxDiskUsed  uint64
	Conn         *grpc.ClientConn
	DB           *storm.DB
	Log          *zap.Logger
}

func NewVolumeManager(req NewVolumeReq) (*Manager, error) {
	ReadOnly := false
	_, err := pathdir.CheckAndMkDir(req.Dir)
	if err != nil {
		panic(err)
	}
	f, err := os.OpenFile(req.Dir, os.O_RDWR, 0)
	if os.IsNotExist(err) {
		panic(err)
	} else if os.IsPermission(err) {
		ReadOnly = true
	}
	f.Close()

	fileInfos, err := ioutil.ReadDir(req.Dir)
	if err != nil {
		log.Fatalln("读取文件夹失败", err)
	}

	vm := new(Manager).initVm(req)
	if ReadOnly == false {
		vm.CanCreateVolume = true
		vm.Writeable = true
	}

	volumeNum := 0
	for _, fi := range fileInfos {
		fileName := fi.Name()
		if strings.HasSuffix(fileName, ".data") {
			vid, err := strconv.ParseUint(fileName[:len(fileName)-5], 10, 64)
			if err != nil {
				log.Fatalln("校验volume失败", err)
			}

			volume, err := NewVolume(req.Dir, vid, req.DB, req.Log)
			if err != nil {
				log.Fatalln("初始化volume失败", err)
			}
			if volume.GetSize() >= int64(vm.MaxVolSize) {
				volume.WriteAble = false
			}
			vm.Volumes[vid] = volume
			volumeNum++
		}
	}
	vm.db = req.DB
	vm.log = req.Log
	vm.HttpHost = req.HttpHost
	vm.VolumeNum = uint32(volumeNum)
	return vm, nil
}

func (vm *Manager) initVm(req NewVolumeReq) *Manager {
	disk := hostinfo.GetDiskInfo(req.Dir)
	vm.DiskFree = disk.Free
	vm.DiskSize = disk.Size
	vm.DiskUsed = disk.Used
	vm.DataDir = req.Dir
	vm.DataCenter = req.DataCenter
	vm.HeartWait = req.HeartWait
	vm.HeartTimeout = req.HeartTimeout
	vm.MaxVolSize = req.MaxVolSize
	vm.MaxDiskUsed = req.MaxDiskUsed
	vm.Machine = fmt.Sprintf("%s-%s", req.StoreName, hostinfo.GetHost().HostID)
	vm.Volumes = make(map[uint64]*Volume)
	vm.Conn = req.Conn
	return vm
}

func (vm *Manager) Start() {
	go vm.heart()
}

func (vm *Manager) Stop() {
	for _, v := range vm.Volumes {
		v.Close()
	}
}

func (vm *Manager) getVms() []*pbfiles.Volume {
	var vms []*pbfiles.Volume
	if vm.VolumeNum > 0 {
		for _, v := range vm.Volumes {
			var size int64
			stat, err := v.dataFile.Stat()
			if err == nil {
				size = stat.Size()
			}
			vms = append(vms, &pbfiles.Volume{Id: v.Id, Writeable: v.WriteAble, VolumeUsed: uint64(size)})
		}
	}
	return vms
}
func (vm *Manager) getDiskSize() (count int64) {
	if vm.VolumeNum > 0 {
		for _, volume := range vm.Volumes {
			count += volume.GetSize()
		}
	}
	return
}

func (vm *Manager) writeAble() bool {
	if vm.Writeable {
		vm.Writeable = vm.MaxDiskUsed > uint64(vm.getDiskSize())
	}
	return vm.Writeable
}

func (vm *Manager) heart() {
	client := pbfiles.NewVolumeHeartClient(vm.Conn)
	disk := hostinfo.GetDiskInfo(vm.DataDir)

	for range time.Tick(time.Second * time.Duration(vm.HeartWait)) {
		ctx, cancel := context.WithTimeout(context.Background(),
			time.Duration(vm.HeartTimeout)*time.Second)
		req := &pbfiles.HeartRequest{
			Host:            vm.HttpHost,
			DiskSize:        disk.Size,
			DiskUsed:        disk.Used,
			DiskFree:        disk.Free,
			Machine:         vm.Machine,
			CanCreateVolume: vm.CanCreateVolume,
			Writeable:       vm.writeAble(),
			VolumeNum:       vm.VolumeNum, // 现在不创建新的volume，故此处不更新
			Volumes:         vm.getVms(),  // 现在不创建新的volume，故此处不更新
		}
		resp, err := client.HeartBeat(ctx, req)
		if err != nil {
			vm.ServerOffline = true
			fmt.Printf("%s %s \r", time.Now().Format("2006-01-02 15:04:05"), "服务器离线，等待连接中...")
			time.Sleep(time.Millisecond * 200)
			continue
		} else {
			if vm.ServerOffline {
				vm.ServerOffline = false
				fmt.Println()
				fmt.Println(time.Now().Format("2006-01-02 15:04:05"), "服务器已连接...")
			}
		}
		if resp.Create {
			vm.createVolume()
		}
		cancel()
	}
}

func (vm *Manager) createBakVolume(vid uint64) (*Volume, error) {
	newVolume, err := NewVolume(vm.DataDir, vid, vm.db, vm.log)
	if err != nil {
		return nil, err
	}
	return newVolume, nil
}

func (vm *Manager) createVolume() *Volume {
	s, _ := snow.NewSnowFlake(0, 0)
	volumeID, _ := s.NextId()
	newVolume, err := NewVolume(vm.DataDir, uint64(volumeID), vm.db, vm.log)
	if err != nil {
		log.Fatalln("初始化volume失败", err)
	}
	vm.Volumes[uint64(volumeID)] = newVolume
	vm.VolumeNum++
	return newVolume
}

func (vm *Manager) getWriteVolume() *Volume {
	for k, v := range vm.Volumes {
		if v.WriteAble {
			if uint64(v.GetSize()) >= vm.MaxVolSize {
				newVol := v
				newVol.WriteAble = false
				vm.Volumes[k] = newVol
			} else {
				return v
			}
		}
	}
	return nil
}

func (vm *Manager) showCMD(server *pbfiles.UploadRequest) {
	fmt.Println()
	fmt.Println()
	table := simpletable.New()
	table.Header = &simpletable.Header{
		Cells: []*simpletable.Cell{
			{Align: simpletable.AlignCenter, Text: "文件名称"},
			{Align: simpletable.AlignCenter, Text: "文件大小"},
			{Align: simpletable.AlignCenter, Text: "FID"},
			{Align: simpletable.AlignCenter, Text: "CRC"},
			{Align: simpletable.AlignCenter, Text: "MD5"},
			{Align: simpletable.AlignCenter, Text: "存储地址"},
			{Align: simpletable.AlignCenter, Text: "实际大小"},
		},
	}
	r := []*simpletable.Cell{
		{Align: simpletable.AlignCenter, Text: server.Name},
		{Align: simpletable.AlignCenter, Text: format.Size(server.Size)},
		{Align: simpletable.AlignCenter, Text: strconv.FormatUint(server.Fid, 10)},
		{Align: simpletable.AlignCenter, Text: strconv.FormatUint(uint64(server.Crc), 10)},
		{Align: simpletable.AlignCenter, Text: server.MD},
		{Align: simpletable.AlignCenter, Text: server.Path},
		{Align: simpletable.AlignCenter, Text: format.Size(uint64(len(server.Data)))},
	}

	table.Body.Cells = append(table.Body.Cells, r)
	table.SetStyle(simpletable.StyleCompactLite)
	fmt.Println(table.String())
}

func (vm *Manager) Upload(ctx context.Context, server *pbfiles.UploadRequest) (*pbfiles.UploadResponse, error) {
	vm.lock.Lock()
	defer vm.lock.Unlock()
	vol := vm.getWriteVolume()
	if vol == nil {
		vol = vm.createVolume()
	}
	volFile, err := vol.NewFile(server.Fid, server.Size, server.Name, server.MD)
	if err != nil {
		vm.log.Error("vol.NewFile", zap.Error(err))
		return &pbfiles.UploadResponse{}, err
	}
	_, err = volFile.Write(server.Data)
	if err != nil {
		vm.log.Error("vol.Write", zap.Error(err))
		return &pbfiles.UploadResponse{}, err
	}
	//vm.showCMD(server)
	return &pbfiles.UploadResponse{
		Msg:  "OK",
		Code: 200,
		Fid:  server.Fid,
		Vid:  vol.Id,
	}, nil
}

func (vm *Manager) GetFile(ctx context.Context, in *pbfiles.FileRequest) (resp *pbfiles.FileResponse, err error) {
	vol, ok := vm.Volumes[in.Vid]
	if !ok {
		return resp, errors.New("volume有误")
	}
	vf, err := vol.Get(in.Fid)
	if err != nil {
		vm.log.Error("vol.Get", zap.Error(err))
		return
	}
	data := make([]byte, vf.Info.Size)
	n, err := vf.Read(data)
	if err != nil {
		vm.log.Error("vol.Read", zap.Error(err))
		return
	}
	if n != int(vf.Info.Size) {
		return resp, errors.New("文件大小读取有误")
	}
	return &pbfiles.FileResponse{Code: 200, Msg: "OK", Data: data}, nil
}

func (vm *Manager) DeleteFile(ctx context.Context, in *pbfiles.DeleteRequest) (resp *pbfiles.DeleteResponse, err error) {
	vol, ok := vm.Volumes[in.Vid]
	if !ok {
		return &pbfiles.DeleteResponse{}, errors.New("vid未找到:" + strconv.FormatUint(in.Vid, 10))
	}
	if err = vol.Delete(in.Fid); err != nil {
		return &pbfiles.DeleteResponse{Code: 404, Msg: "文件不存在"}, err
	}
	return &pbfiles.DeleteResponse{Code: 200, Msg: "OK"}, nil
}
